package hk.edu.polyu.comp.motioncapture;

//import java.util.Iterator;
import org.eclipse.swt.graphics.Rectangle;
import java.util.Vector;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.StatusLineManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.Image;
import com.swtdesigner.ResourceManager;
import com.swtdesigner.SWTResourceManager;

public class MotionCapture extends ApplicationWindow {

	private final static int REFRESH_TIME_PER_SECOND = 20;
	private final static int PREDICT_COUNT = 5;
	private final static double PREDICT_RADIUS = 20.0;
	private final static double PREDICT_RATE = 0.8;
	private final static int DRAW_POINT_LIMIT = 16;

	private List pointTestList;
	protected Shell shell;
	protected Canvas pointTestCanvas;

	private Vector<TrackPoint> pointList;
	private TrackPoint predictPoint;

	private int hitCount;
	private int missCount;

	private Action aboutAction;
	private Action exitAction;

	/**
	 * Create the application window
	 */
	public MotionCapture() {
		super(null);
		createActions();
		addToolBar(SWT.FLAT | SWT.WRAP);
		addMenuBar();
		addStatusLine();

		initialize();
	}

	/**
	 * Initialize the application
	 */
	private void initialize() {
		if (pointTestList != null)
			pointTestList.removeAll();

		pointList = new Vector<TrackPoint>();
		predictPoint = new TrackPoint();
		hitCount = 0;
		missCount = 0;
	}

	/**
	 * Create contents of the application window
	 * 
	 * @param parent
	 */
	@Override
	protected Control createContents(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		container.setLayout(new FillLayout());

		final TabFolder tabFolder = new TabFolder(container, SWT.NONE);

		final TabItem pointTestTabItem = new TabItem(tabFolder, SWT.NONE);
		pointTestTabItem.setText("Point Test");

		final Composite composite = new Composite(tabFolder, SWT.NONE);
		final GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 2;
		composite.setLayout(gridLayout);
		pointTestTabItem.setControl(composite);

		pointTestCanvas = new Canvas(composite, SWT.BORDER | SWT.NO_BACKGROUND);
		final GridData gd_pointTestCanvas = new GridData(SWT.FILL, SWT.FILL,
				true, true, 1, 3);
		gd_pointTestCanvas.minimumHeight = 80;
		gd_pointTestCanvas.minimumWidth = 80;

		pointTestCanvas.setLayoutData(gd_pointTestCanvas);
		pointTestCanvas.addMouseListener(new MouseListener() {
			public void mouseUp(MouseEvent e) {
				// shell.getDisplay().syncExec(new Runnable() {
				// public void run() {
				// if (shell != null) {
				// }
				// }
				// });
			}

			public void mouseDown(MouseEvent e) {
				String title = "";
				TrackPoint currPt = new TrackPoint(e.x, e.y);

				// Test if hit prediction
				if (pointList.size() > 1) {
					if (predictPoint.dx2 >= Math
							.sqrt((predictPoint.x - currPt.x)
									* (predictPoint.x - currPt.x)
									+ (predictPoint.y - currPt.y)
									* (predictPoint.y - currPt.y))) {
						hitCount++;
						title += "Y ";
					} else {
						missCount++;
						title += "N ";
					}

					title += "r: "
							+ (int) predictPoint.dx2
							+ "  sqrt: "
							+ (int) Math.sqrt((predictPoint.x - currPt.x)
									* (predictPoint.x - currPt.x)
									+ (predictPoint.y - currPt.y)
									* (predictPoint.y - currPt.y)) + " pp: ("
							+ (int) predictPoint.x + ", "
							+ (int) predictPoint.y + ") cp: (" + (int) currPt.x
							+ ", " + (int) currPt.y + ")";

					title += "  Hit Count: "
							+ hitCount
							+ " ("
							+ (int) ((double) hitCount
									/ (double) (hitCount + missCount) * 100.0)
							+ "%)";
				}

				// Calculate new point
				if (pointList.size() > 0) {
					TrackPoint prevPt;
					prevPt = pointList.lastElement();

					currPt.dx = currPt.x - prevPt.x;
					currPt.dx2 = currPt.dx - prevPt.dx;

					currPt.dy = currPt.y - prevPt.y;
					currPt.dy2 = currPt.dy - prevPt.dy;

					currPt.previous = prevPt;
					prevPt.next = currPt;
				}
				pointList.add(currPt);

				int cnt = 0;
				double dx2Sum = 0.0, dy2Sum = 0.0;
				for (TrackPoint pt = currPt; pt != null && cnt <= PREDICT_COUNT; pt = pt.previous, cnt++) {
					dx2Sum += pt.dx2;
					dy2Sum += pt.dy2;
				}

				// Predict point
				predictPoint.x = currPt.x
						+ (currPt.dx + (dx2Sum / (double) cnt));
				predictPoint.y = currPt.y
						+ (currPt.dy + (dy2Sum / (double) cnt));

				predictPoint.dx = predictPoint.x - currPt.x;
				predictPoint.dy = predictPoint.y - currPt.y;

				double predictDistance = Math
						.sqrt((predictPoint.dx * predictPoint.dx)
								+ (predictPoint.dy * predictPoint.dy));
				if (predictDistance > PREDICT_RADIUS)
					predictPoint.dx2 = PREDICT_RADIUS
							+ (predictDistance - PREDICT_RADIUS) * PREDICT_RATE;
				else
					predictPoint.dx2 = PREDICT_RADIUS;
				// predictPoint.dy2 = predictDistance;

				// Add to Point List
				pointTestList.add("" + Integer.toString((int) currPt.x) + ", "
						+ Integer.toString((int) currPt.y) + " | "
						+ Integer.toString((int) currPt.dx) + ", "
						+ Integer.toString((int) currPt.dy) + " | "
						+ Integer.toString((int) predictPoint.x) + ", "
						+ Integer.toString((int) predictPoint.y) + "");

				shell.setText(title);
				shell.getDisplay().timerExec(1000 / REFRESH_TIME_PER_SECOND, new Runnable() {
					public void run() {
						if (!pointTestCanvas.isDisposed())
							pointTestCanvas.redraw();
					}
				});
			}

			public void mouseDoubleClick(MouseEvent e) {
			}

		});
		pointTestCanvas.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {

				// GC gc = event.gc;

				Image image = (Image) pointTestCanvas
						.getData("double-buffer-image");
				if (image == null
						|| image.getBounds().width != pointTestCanvas.getSize().x
						|| image.getBounds().height != pointTestCanvas
								.getSize().y) {
					image = new Image(shell.getDisplay(), pointTestCanvas
							.getSize().x, pointTestCanvas.getSize().y);
					pointTestCanvas.setData("double-buffer-image", image);
				}
				// Initializes the graphics context of the image.
				GC gc = new GC(image);
				gc.setAntialias(SWT.ON);

				gc.setBackground(event.gc.getBackground());
				gc.setForeground(event.gc.getForeground());
				gc.setFont(event.gc.getFont());

				// Fills background.
				Rectangle imageSize = image.getBounds();
				gc.fillRectangle(0, 0, imageSize.width + 1,
						imageSize.height + 1);

				// Draw lines
				gc.setBackground(shell.getDisplay().getSystemColor(
						SWT.COLOR_BLACK));
				gc.setForeground(shell.getDisplay().getSystemColor(
						SWT.COLOR_GRAY));

				if (pointList != null && pointList.size() > 0) {
					// int cnt = 0;
					TrackPoint pt = pointList.lastElement();
					TrackPoint prevPt = null;
					int cnt = 0;
					while (pt != null && cnt < DRAW_POINT_LIMIT) {
						if (prevPt != null) {
							gc.drawLine((int) pt.x, (int) pt.y, (int) prevPt.x,
									(int) prevPt.y);
						}
						gc.fillOval((int) pt.x, (int) pt.y, 4, 4);

						prevPt = pt;
						pt = pt.previous;

						cnt++;
					}
				}

				// Draw prediction
				if (pointList.size() > 1) {
					gc.setBackground(shell.getDisplay().getSystemColor(
							SWT.COLOR_YELLOW));
					gc.fillOval((int) predictPoint.x, (int) predictPoint.y, 4,
							4);
					gc.setForeground(shell.getDisplay().getSystemColor(
							SWT.COLOR_RED));
					gc.drawOval((int) (predictPoint.x - (predictPoint.dx2)),
							(int) (predictPoint.y - (predictPoint.dx2)),
							(int) predictPoint.dx2 * 2,
							(int) predictPoint.dx2 * 2);
				}
				// Rectangle rect = pointTestCanvas.getClientArea();
				// gc.drawOval(0, 0, rect.width - 1, rect.height - 1);
				// gc.drawOval(10, 10, 50, 50);

				// Draws the buffer image onto the canvas.
				event.gc.drawImage(image, 0, 0);

				gc.dispose();

				shell.getDisplay().timerExec(1000 / REFRESH_TIME_PER_SECOND, new Runnable() {
					public void run() {
						if (!pointTestCanvas.isDisposed())
							pointTestCanvas.redraw();
					}
				});
			}
		});
		// refreshThread(shell, pointTestCanvas).start();

		final Button clearButton = new Button(composite, SWT.NONE);
		clearButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				initialize();

				shell.getDisplay().timerExec(1000 / REFRESH_TIME_PER_SECOND, new Runnable() {
					public void run() {
						if (!pointTestCanvas.isDisposed())
							pointTestCanvas.redraw();
					}
				});
			}
		});
		final GridData gd_clearButton = new GridData(SWT.FILL, SWT.TOP, false,
				false);
		clearButton.setLayoutData(gd_clearButton);
		clearButton.setText("&Clear Frame");

		final Button saveDataButton = new Button(composite, SWT.NONE);
		saveDataButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(final SelectionEvent e) {
				//
			}
		});
		saveDataButton.setEnabled(false);
		final GridData gd_saveDataButton = new GridData(SWT.FILL, SWT.TOP,
				false, false);
		saveDataButton.setLayoutData(gd_saveDataButton);
		saveDataButton.setText("&Save Data");

		pointTestList = new List(composite, SWT.V_SCROLL | SWT.BORDER);
		final GridData gd_pointTestList = new GridData(SWT.FILL, SWT.FILL,
				false, false);
		gd_pointTestList.widthHint = 160;
		pointTestList.setLayoutData(gd_pointTestList);
		//
		return container;
	}

	/**
	 * Create the actions
	 */
	private void createActions() {

		exitAction = new Action("E&xit") {
			public void run() {
				// System.exit(0);
				shell.dispose();
			}
		};

		aboutAction = new Action("&About...") {
			public void run() {
				MessageBox messageBox = new MessageBox(shell,
						SWT.ICON_INFORMATION | SWT.OK);
				messageBox.setText("About Motion Capture");
				messageBox
						.setMessage("A motion capture project by:\n\nWill TANG Wai Wa\nKenneth LO Wai Kwan\n\nDepartment of Computing\nThe Hong Kong Polytechnic University");
				messageBox.open();
			}
		};
		aboutAction.setImageDescriptor(ResourceManager.getImageDescriptor(
				MotionCapture.class, "image/about.png"));
		// Create the actions
	}

	/**
	 * Create the menu manager
	 * 
	 * @return the menu manager
	 */
	@Override
	protected MenuManager createMenuManager() {
		MenuManager menuManager = new MenuManager("menu");

		final MenuManager fileMenuManager = new MenuManager("&File");
		menuManager.add(fileMenuManager);

		fileMenuManager.add(exitAction);

		final MenuManager helpMenuManager = new MenuManager("&Help");
		menuManager.add(helpMenuManager);

		helpMenuManager.add(aboutAction);
		return menuManager;
	}

	/**
	 * Create the toolbar manager
	 * 
	 * @return the toolbar manager
	 */
	@Override
	protected ToolBarManager createToolBarManager(int style) {
		ToolBarManager toolBarManager = new ToolBarManager(style);

		toolBarManager.add(aboutAction);
		return toolBarManager;
	}

	/**
	 * Create the status line manager
	 * 
	 * @return the status line manager
	 */
	@Override
	protected StatusLineManager createStatusLineManager() {
		StatusLineManager statusLineManager = new StatusLineManager();
		statusLineManager.setMessage(null, "");
		return statusLineManager;
	}

	/**
	 * Launch the application
	 * 
	 * @param args
	 */
	public static void main(String args[]) {
		try {
			MotionCapture window = new MotionCapture();
			window.setBlockOnOpen(true);
			window.open();
			Display.getCurrent().dispose();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Configure the shell
	 * 
	 * @param newShell
	 */
	@Override
	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);
		shell = newShell;

		newShell.setText("Motion Capture");
		newShell.setImage(SWTResourceManager.getImage(MotionCapture.class,
				"image/hand.png"));
	}

	/**
	 * Return the initial size of the window
	 */
	@Override
	protected Point getInitialSize() {
		return new Point(400, 320);
	}

	/**
	 * 
	 * @param Shell,
	 *            Canvas
	 * @return Thread
	 */
	public static Thread refreshThread(Shell shell, Canvas canvas) {
		final Shell theShell = shell;
		final Canvas theCanvas = canvas;
		return new Thread() {
			public void run() {
				try {
					// final int intvl = 1000 / REFRESH_TIME_PER_SECOND;
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				theShell.getDisplay().asyncExec(new Runnable() {
					public void run() {
						theShell.setText("5 sec");
						theCanvas.redraw();
					}
				});
			}
		};
	}
}
